﻿using System.Linq;
using RoMBot.MemoryLib;

namespace RoMBot.Classes
{
    public class GameObject
    {
        protected BaseGameObject baseObject;
        internal const string UnknowName = "<UNKNOWN>";
        protected const int AttackableMaskPlayer = 0x10000;
        protected const int AttackableMaskMonster = 0x20000;
        protected const int AttackableMaskClickable = 0x1000;
        protected const int AggressiveMaskMonster = 0x100000;


        public GameObject( int address )
        {
            this.Id = -1;
            if ( address != 0 )
            {
                this.Address = address;

                if ( this.Address == Addresses.staticbase_char ) // we came here called from default constructor in Player class
                {
                    this.Address = Memory.ReadIntPtr( Addresses.staticbase_char, Addresses.charPtr_offset );
                }

                UpdateFromBaseObject();
            }
        }

        public virtual void Update()
        {
            UpdateFromBaseObject();
            this.UpdateId();
        }

        private void UpdateFromBaseObject()
        {
            baseObject = Memory.ReadObject<BaseGameObject>( this.Address ) ?? new BaseGameObject();
            this.Type = baseObject.Type;
            this.Name = baseObject.Name;
            this.Level = baseObject.Level;
            this.GUID = baseObject.GUID;
            this.IsPet = baseObject.IsPet;
            this.HP = baseObject.HP;
            this.MaxHP = baseObject.MaxHP;
            this.targetPtr = baseObject.TargetPtr;

            if ( string.IsNullOrEmpty( this.Name ) || this.Name == UnknowName )
            {
                nameUpdated = false;
                UpdateName();
            }
            else
            {
                nameUpdated = true;
            }
            if ( this.Id != baseObject.Id )
            {
                UpdateId();
            }
        }

        public void UpdateId()
        {
            if ( !this.HasAddress )
            {
                this.Id = 0;
                this.Type = 0;
                this.Name = UnknowName;
                return;
            }
            var tmp = baseObject != null ? baseObject.Id : ( int ) Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnId_offset );
            if ( this.Id == -1 ) //First time. Get it.
            {
                this.Id = tmp;
            }
            if ( this.Id > 999999 )
            {
                this.Id = 0;
            }
            else if ( this.Id >= 1000 && this.Id <= 1004 ) //player ids can change
            {
                if ( tmp >= 1000 && tmp <= 1004 )
                {
                    this.Id = tmp;
                }
            }
            else //see if it changed
            {
                if ( tmp != this.Id ) // Id changed. Pawn no longer valid
                {
                    this.Id = 0;
                    this.Type = 0;
                    this.Name = UnknowName;
                }
            }
        }

        private bool nameUpdated;
        public void UpdateName()
        {
            if ( nameUpdated )
            {
                return;
            }
            if ( !this.HasAddress )
            {
                this.Name = UnknowName;
                return;
            }
            string tmp = null;
            var namePtr = baseObject.NamePointer; //Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnName_offset );
            if ( namePtr /*baseObject.NamePtr*/ != 0 )
            {
                tmp = Memory.ReadUtf8String( namePtr /*baseObject.NamePtr */);
            }
            this.Name = tmp ?? UnknowName;
            nameUpdated = true;
        }

        public int GUID
        {
            get;
            set;
        }

        public float DistanceTo( GameObject obj )
        {
            return Helpers.Distance( this.X, this.Z, this.Y, obj.X, obj.Z, obj.Y );
        }

        public float DistanceToWaypoint( Waypoints.Waypoint wp )
        {
            return Helpers.Distance( this.X, this.Z, this.Y, wp.X, wp.Z, wp.Y );
        }

        public bool Aggressive
        {
            get
            {
                var aFlag = AttackableFlag;
                return this.Type == ObjectType.Monster && aFlag != 0 && Helpers.BitAnd( aFlag, AggressiveMaskMonster );
            }
        }

        public int Address { get; set; }

        public string Name { get; set; }

        public int Id { get; set; }

        public virtual int Level { get; set; }

        public bool IsPet { get; set; }

#pragma warning disable 1570
        /// <summary>
        /// TODO: Check if type can cahnge, if it can, then we need:
        /// ( ObjectType ) Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnType_offset );
        /// </summary>
#pragma warning restore 1570
        public ObjectType Type { get; set; }

        public float X
        {
            get { return Location.X; }
        }

        public float Y
        {
            get { return Location.Y; }
        }

        public float Z
        {
            get { return Location.Z; }
        }

        public float Distance { get; set; }

        public bool HasAddress
        {
            get { return this.Address > 0; }
        }

        public virtual bool Exists
        {
            get
            {
                if ( !this.HasAddress )
                {
                    return false;
                }
                this.UpdateId();
                return this.Id != 0;
            }
        }

        internal int AttackableFlag
        {
            get
            {
                return !this.HasAddress ? 0 : Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnAttackable_offset );
            }
        }

        public bool Attackable
        {
            get
            {
                var aFlag = AttackableFlag;
                return this.Type == ObjectType.Monster && aFlag != 0 && Helpers.BitAnd( aFlag, AttackableMaskMonster ) && Helpers.BitAnd( aFlag, AttackableMaskClickable );
            }
        }

        private BaseGameObjectLocation location;
        internal float hp = 1000f;
        internal float maxHP = 1000f;
        public int targetPtr = 0;

        public BaseGameObjectLocation Location
        {
            get
            {
                this.location = this.HasAddress ? Memory.ReadObject<BaseGameObjectLocation>( this.Address ) : new BaseGameObjectLocation();
                return location;
            }
            internal set { location = value; }
        }

        //public virtual int TargetPtr
        //{
        //    get { return this.HasAddress ? baseObject.TargetPtr : 0; }
        //    set { baseObject.TargetPtr = value; }
        //}

        public float HP
        {
            get
            {
                this.hp = !this.HasAddress ? 0 : Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnHP_offset );
                return this.hp;
            }
            set { this.hp = value; }
        }

        public float MaxHP
        {
            get
            {
                this.maxHP = !this.HasAddress ? 1 : Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnMaxHP_offset );
                return this.maxHP;
            }
            set { this.maxHP = value; }
        }

        /// <summary>
        /// Property returns always current value from memory, if you want to check for manually set value, use the field:
        /// '<b>targetPtr</b>'
        /// </summary>
        public int TargetPtr
        {
            get { return !this.HasAddress ? 0 : ( int ) Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnTargetPtr_offset ); }
            set
            {
                this.targetPtr = value;
                if ( value >= 0 )
                {
                    Memory.WriteInt( this.Address + Addresses.pawnTargetPtr_offset, this.targetPtr );
                }
            }
        }

        public bool IsFriend( bool aggroOnly = false )
        {
            if ( !this.Exists )
                return false;

            //this.UpdateName();
            this.UpdateId();

            int i;
            return Bot.Player.Profile.Friends.Any( f => this.Name.ToLowerInvariant().Contains( f.ToLowerInvariant() ) ) || Bot.Player.Profile.Friends.Any( f => int.TryParse( f, out i ) && i == this.Id );
        }

        public bool TargetIsFriend( bool aggroOnly = false )
        {
            if ( !this.Exists )
                return false;

            if ( this.targetPtr == 0 )
            {
                return false;
            }

            //pets are friends
            if ( Bot.Player.PetPtr != 0 && this.Address == Bot.Player.PetPtr )
            {
                return true;
            }

            if ( aggroOnly )
            {
                return false;
            }

            //if ( this.Target == null )
            //{
            //    this.UpdateTarget();
            //}
            var target = new GameObject( this.targetPtr );
            return target.IsFriend( aggroOnly );
        }
    }
}
